package educrypt;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Locale;

import javax.swing.JComponent;

import educrypt.database.EduCryptDatabase;
import educrypt.exceptions.EduCryptException;
import educrypt.gui.components.EduCryptGui;
import educrypt.i18n.Messages;

public class EduCryptMain {

	public static void main(String[] args) {
		// Change locale if indicated
		boolean onReset = processLocaleArguments(args);

		// Close the current instance if previous instance is running
		// If a previous instance is running, it will be notified
		closeIfInstanceRunning(onReset);

		// Create the database if does not exists
		createDatabaseIfNotExists();

		// GUI initialization
		EduCryptGui.initialize();
	}

	private static void closeIfInstanceRunning(boolean onReset) {
		if (!ApplicationInstanceManager.registerInstance(onReset)) {
			// instance already running.
			System.out
					.println("Another instance of this application is already running.  Exiting.");
			System.exit(0);
		}

		// Listener binding
		ApplicationInstanceManager
				.setApplicationInstanceListener(new ApplicationInstanceListener() {
					public void newInstanceCreated() {
						System.out.println("New instance detected...");
						// Bring the window to the front
						EduCryptGui.toFront();
					}
				});
	}

	private static boolean processLocaleArguments(String[] args) {
		boolean argumentsExist = args.length > 0;
		if (argumentsExist) {
			Messages.setLocale(new Locale(args[0]));
			JComponent.setDefaultLocale(Messages.getLocale());
		}
		return argumentsExist;
	}

	private static void createDatabaseIfNotExists() {
		// Database creation (If it not exists)
		EduCryptDatabase eduCryptDatabase = EduCryptDatabase.getInstance();
		if (!eduCryptDatabase.existsDatabase()) {
			try {
				eduCryptDatabase.createDatabase();
			} catch (EduCryptException e) {
				e.printStackTrace();
			}
		}
	}
}

class ApplicationInstanceManager {

	private static ApplicationInstanceListener subListener;

	/** Randomly chosen, but static, high socket number */
	public static final int SINGLE_INSTANCE_NETWORK_SOCKET = 44331;

	/** Must end with newline */
	public static final String SINGLE_INSTANCE_SHARED_KEY = "$$NewInstance$$\n";

	public static boolean registerInstance(final boolean onReset) {
		// returnValueOnError should be true if lenient (allows app to run on
		// network error) or false if strict.
		boolean returnValueOnError = true;

		// try to open network socket
		// if success, listen to socket for new instance message, return true
		// if unable to open, connect to existing and send new instance message,
		// return false
		final int MAX_TRIES = 10;
		int tries = 0;
		boolean socketOpened = false;

		while (tries < MAX_TRIES && !socketOpened) {
			try {
				final ServerSocket socket = new ServerSocket(
						SINGLE_INSTANCE_NETWORK_SOCKET, 10,
						InetAddress.getLocalHost());

				// It will be true if the creation of the socket does not throw
				// an exception
				socketOpened = true;

				// This is the listener thread
				// It will execute if this is the first instance of the program
				Thread instanceListenerThread = new Thread(new Runnable() {
					public void run() {
						boolean socketClosed = false;
						while (!socketClosed) {
							if (socket.isClosed()) { // When the socket is
														// closed
														// the loop stops
								socketClosed = true;
							} else {
								// Checking if other instance sent a msg
								// througth
								// the socked
								try {
									Socket client = socket.accept();
									BufferedReader in = new BufferedReader(
											new InputStreamReader(
													client.getInputStream()));
									String message = in.readLine();
									if (SINGLE_INSTANCE_SHARED_KEY.trim()
											.equals(message.trim())) {
										fireNewInstance();
									}
									in.close();

									client.close();
								} catch (IOException e) {
									socketClosed = true;
								}
							}
						}
					}
				});
				
				// listen
				instanceListenerThread.start();
			} catch (UnknownHostException e) {
				return returnValueOnError;
			} catch (IOException e) { // Exception opening the socket
				if (onReset) { // Workaround to allow application reset
					try {
						Thread.sleep(250);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					tries++; // Try to open the connection again
				} else {
					try {// The program send a message to the other instance
						Socket clientSocket = new Socket(
								InetAddress.getLocalHost(),
								SINGLE_INSTANCE_NETWORK_SOCKET);
						OutputStream out = clientSocket.getOutputStream();
						out.write(SINGLE_INSTANCE_SHARED_KEY.getBytes());
						out.close();
						clientSocket.close();

						return false;
					} catch (UnknownHostException e1) {
						return returnValueOnError;
					} catch (IOException e1) {
						return returnValueOnError;
					}
				}
			}
		}
		if (tries == MAX_TRIES) {
			return returnValueOnError;
		}
		return true;
	}

	public static void setApplicationInstanceListener(
			ApplicationInstanceListener listener) {
		subListener = listener;
	}

	private static void fireNewInstance() {
		if (subListener != null) {
			subListener.newInstanceCreated();
		}
	}
}

interface ApplicationInstanceListener {
	public void newInstanceCreated();
}